﻿using Elasticsearch.Net;
using Nest;
using Nest.JsonNetSerializer;
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Linq;
using System.Threading.Tasks;
using System.Collections.Concurrent;
using System.IO;
using System.Reflection;
using System.Text;
using Newtonsoft.Json;

namespace ElasticSearchRealize.QueryProvider
{
    /// <summary>
    /// ES操作，适用于ElasticSearch 7.x版本
    /// </summary>
    public class ElasticSearchHelper
    {
        /// <summary>
        /// 包含了已知索引
        /// </summary>
        private static readonly ConcurrentDictionary<string, byte[]> IndexNames = new ConcurrentDictionary<string, byte[]>();

        /// <summary>
        /// 检查不存在的索引时间，单位：分钟
        /// </summary>
        private const int MinuteOfCheckIndexExists = 2;

        /// <summary>
        /// 不存在的索引名称和最后检查时间
        /// </summary>
        private static ConcurrentDictionary<string, DateTime> NotExistsIndexNames = new ConcurrentDictionary<string, DateTime>();

        /// <summary>
        /// ES服务地址
        /// </summary>
        private static string Server = "amiao:9201";// ConfigurationManager.AppSettings["ElasticSearchServer"];
        //private static string Server = "ebs_es:9200";
        /// <summary>
        /// 用户名
        /// </summary>
        private static string UserName = "elastic";
        /// <summary>
        /// 密码
        /// </summary>
        private static string UserPwd = "Qwer.1234";

        /// <summary>
        /// The elastic client
        /// </summary>
        private static ElasticClient ElasticClient = null;

        /// <summary>
        /// Gets the elastic client.
        /// </summary>
        /// <returns>ElasticClient.</returns>
        public static ElasticClient GetElasticClient()
        {
            if (ElasticClient == null)
            {
                lock (IndexNames)
                {
                    if (ElasticClient == null)
                    {
                        var pool = new SingleNodeConnectionPool(new Uri($"http://{Server}")) ;
                        var settings = new ConnectionSettings(pool, (builtIn, values) => new IsoDateFormatSerializer(builtIn, values))
                            .BasicAuthentication(UserName, UserPwd)
                            .DefaultFieldNameInferrer(x => x);
                        ElasticClient = new ElasticClient(settings);
                    }
                }
            }
            return ElasticClient;
        }


        /// <summary>
        /// Gets the elastic client.
        /// </summary>
        /// <returns>ElasticClient.</returns>
        public static ElasticClient GetElasticClient(string indexName)
        {
            if (ElasticClient == null)
            {
                lock (IndexNames)
                {
                    if (ElasticClient == null)
                    {
                        var pool = new SingleNodeConnectionPool(new Uri($"http://{Server}"));
                        var settings = new ConnectionSettings(pool, (builtIn, values) => new IsoDateFormatSerializer(builtIn, values))
                            .DefaultIndex(indexName)
                            .DefaultFieldNameInferrer(x => x);
                        ElasticClient = new ElasticClient(settings);
                    }
                }
            }
            return ElasticClient;
        }

        /// <summary>
        ///  查看索引是否存在
        /// </summary>
        /// <param name="indexName">索引的名字</param>
        /// <returns>该索引是否存在，true表示存在</returns>
        public static bool IfIndexExists(string indexName)
        {
            //try
            //{
            //    if (IndexNames.ContainsKey(indexName))
            //        return true;
            //    var aliasResp = GetElasticClient().Indices.GetAlias();
            //    if (aliasResp.IsValid)
            //    {
            //        foreach (var index in aliasResp.Indices.Keys)
            //        {
            //            if (!IndexNames.ContainsKey(index.Name))
            //            {
            //                IndexNames.TryAdd(index.Name, null);
            //            }
            //        }
            //        return IndexNames.ContainsKey(indexName);
            //    }

            //    var request = Indices.Index(indexName);
            //    var resp = GetElasticClient().Indices.Exists(request);
            //    if (resp.Exists)
            //    {
            //        IndexNames.TryAdd(indexName, null);
            //        return true;
            //    }
            //    return false;
            //}
            //catch
            //{
            //    return false;
            //}
            if (IndexNames.ContainsKey(indexName))
                return true;
            if (NotExistsIndexNames.ContainsKey(indexName) && (DateTime.Now - NotExistsIndexNames[indexName]).TotalMinutes < MinuteOfCheckIndexExists)
                return false;

            if (InitExistsIndexNames() && IndexNames.ContainsKey(indexName))
            {
                return true;
            }
            var exists = ExistsIndexByRequest(indexName);
            if (!exists)
                AddNotExistsIndexName(indexName);
            return exists;
        }

        /// <summary>
        /// To the query string.
        /// </summary>
        /// <typeparam name="TEntity">The type of the t entity.</typeparam>
        /// <param name="searchDesc">The search desc.</param>
        /// <returns>System.String.</returns>
        public static string ToQueryString<TEntity>(SearchDescriptor<TEntity> searchDesc) where TEntity : class
        {
            var settings = new ConnectionSettings().DefaultFieldNameInferrer(x => x);
            SerializableData<SearchDescriptor<TEntity>> serializableData = searchDesc;
            using (var stream = new MemoryStream())
            {
                serializableData.Write(stream, settings);
                stream.Position = 0;
                using (var sr = new StreamReader(stream))
                {
                    return sr.ReadToEnd();
                }
            }
        }

        /// <summary>
        /// 在ES中通过SQL的方式查询数据
        /// </summary>
        /// <typeparam name="TResult">返回的集合中数据实体类型.</typeparam>
        /// <param name="sql">要执行的SQL.</param>
        /// <returns>SQL执行返回的数据对象</returns>
        /// <exception cref="Exception"></exception>
        public static async Task<IEnumerable<TResult>> ExecuteSQLAsync<TResult>(string sql)
            where TResult : class, new()
        {
            var stopwatch = new System.Diagnostics.Stopwatch();
            stopwatch.Start();
            var fetchSize = 1000;
            var sqlQuery = new QuerySqlRequest { Query = sql, FetchSize = fetchSize, Format = "json" };
            var resp = await GetElasticClient().Sql.QueryAsync(sqlQuery);
            if (!resp.IsValid)
                throw new Exception($"执行ES查询出错,Error:{resp.GetErrorString()}， SQL：{sql}", resp.OriginalException);
            if (resp.Rows == null || resp.Rows.Count == 0)
                return new TResult[0];

            var index = 0;
            var columnIndex = resp.Columns.ToDictionary(x => x.Name, x => index++);
            var indexPropertyMap = ExtractColumnIndexPropertyMap<TResult>(columnIndex);
            var result = new List<TResult>(fetchSize);
            var rows = DeserializationJsonRows<TResult>(resp.Rows, indexPropertyMap);
            result.AddRange(rows);

            while (resp.Rows != null && resp.Rows.Count >= fetchSize && !string.IsNullOrWhiteSpace(resp.Cursor))
            {
                sqlQuery = new QuerySqlRequest { Cursor = resp.Cursor };
                resp = await GetElasticClient().Sql.QueryAsync(sqlQuery);
                rows = DeserializationJsonRows<TResult>(resp.Rows, indexPropertyMap);
                result.AddRange(rows);
            }
            if (!string.IsNullOrWhiteSpace(resp.Cursor))
            {
                await GetElasticClient().Sql.ClearCursorAsync(new ClearSqlCursorRequest { Cursor = resp.Cursor });
            }
            stopwatch.Stop();
            //if (stopwatch.ElapsedMilliseconds > 3000)
            //{
            //    Console.WriteLine($"ES数据查询耗时：{stopwatch.ElapsedMilliseconds}ms，SQL：{sql}");
            //}
            Console.WriteLine($"ES数据查询耗时：{stopwatch.ElapsedMilliseconds}ms，SQL：{sql}");
            return result;
        }

        /// <summary>
        /// 反序列化Json格式的行数据
        /// </summary>
        /// <typeparam name="TResult"></typeparam>
        /// <param name="rows"></param>
        /// <param name="indexPropertyMap"></param>
        /// <returns></returns>
        private static List<TResult> DeserializationJsonRows<TResult>(IReadOnlyCollection<SqlRow> rows, Dictionary<int, PropertyInfo> indexPropertyMap) where TResult : class, new()
        {
            if (rows == null || rows.Count == 0)
                return new List<TResult>();
            var result = new List<TResult>();
            foreach (var row in rows)
            {
                var item = DeserializationJsonRow<TResult>(row, indexPropertyMap);
                result.Add(item);
            }
            return result;
        }

        /// <summary>
        /// 反序列化Json格式的行数据
        /// </summary>
        /// <typeparam name="TResult"></typeparam>
        /// <param name="rows"></param>
        /// <param name="indexPropertyMap"></param>
        /// <returns></returns>
        private static TResult DeserializationJsonRow<TResult>(SqlRow row, Dictionary<int, PropertyInfo> indexPropertyMap)
        {
            var item = Activator.CreateInstance<TResult>();
            for (var i = 0; i < row.Count; i++)
            {
                if (!indexPropertyMap.ContainsKey(i))
                    continue;
                if (row[i] == null)
                {
                    continue;
                }
                object value;
                if (indexPropertyMap[i].PropertyType == typeof(int))
                {
                    value = Convert.ToInt32(row[i].As<object>());
                }
                else
                {
                    value = row[i].As(indexPropertyMap[i].PropertyType);
                }
                indexPropertyMap[i].SetValue(item, value);
            }
            return item;
        }

        /// <summary>
        /// Extracts the column index property map.
        /// </summary>
        /// <typeparam name="TResult">The type of the t result.</typeparam>
        /// <param name="columnIndex">Index of the column.</param>
        /// <returns>Dictionary&lt;System.Int32, PropertyInfo&gt;.</returns>
        private static Dictionary<int, PropertyInfo> ExtractColumnIndexPropertyMap<TResult>(Dictionary<string, int> columnIndex)
        {
            var resultType = typeof(TResult);
            var indexPropertyMap = new Dictionary<int, PropertyInfo>();
            var properties = resultType.GetProperties();
            foreach (var propertyInfo in properties)
            {
                if (!propertyInfo.CanWrite)
                    continue;
                var propertyName = propertyInfo.Name;
                if (columnIndex.ContainsKey(propertyName))
                {
                    indexPropertyMap.Add(columnIndex[propertyName], propertyInfo);
                    continue;
                }
                var kv = columnIndex.FirstOrDefault(x => x.Key.Equals(propertyName, StringComparison.OrdinalIgnoreCase));
                if (!string.IsNullOrWhiteSpace(kv.Key) && !indexPropertyMap.ContainsKey(kv.Value))
                {
                    indexPropertyMap.Add(kv.Value, propertyInfo);
                }
            }
            return indexPropertyMap;
        }

        /// <summary>
        /// 提取在ES索引中存在的索引名称
        /// </summary>
        /// <param name="indices">待校验的索引名称集合.</param>
        /// <returns>存在的索引名称集合.</returns>
        public static string[] ExtractExistsIndices(string[] indices)
        {
            if (indices == null || indices.Length == 0)
                return indices;
            var existsIndices = new List<string>(indices.Length);
            var initializedExistsIndex = false;
            foreach (var indexName in indices)
            {
                if (IndexNames.ContainsKey(indexName))
                {
                    existsIndices.Add(indexName);
                    continue;
                }
                if (NotExistsIndexNames.ContainsKey(indexName) && (DateTime.Now - NotExistsIndexNames[indexName]).TotalMinutes < MinuteOfCheckIndexExists)
                    continue;
                if (!initializedExistsIndex)
                {
                    initializedExistsIndex = true;
                    InitExistsIndexNames();
                }
                if (IndexNames.ContainsKey(indexName))
                {
                    existsIndices.Add(indexName);
                    continue;
                }
                if (!initializedExistsIndex && ExistsIndexByRequest(indexName))
                {
                    existsIndices.Add(indexName);
                    continue;
                }
                AddNotExistsIndexName(indexName);
            }
            return existsIndices.ToArray();
        }

        /// <summary>
        /// 提取在ES索引中不能存在的索引名称
        /// </summary>
        /// <param name="indices">待校验的索引名称集合.</param>
        /// <returns>不存在的索引名称集合.</returns>
        public static string[] ExtractNotExistsStatsIndices(string[] indices)
        {
            if (indices == null || indices.Length == 0)
                return indices;

            var hasNotExistsIndex = false;
            foreach (var index in indices)
            {
                if (!IndexNames.ContainsKey(index))
                {
                    hasNotExistsIndex = true;
                    break;
                }
            }
            if (!hasNotExistsIndex)
                return new string[0];

            var notExistsIndices = new List<string>(indices.Length);
            if (!InitExistsIndexNames())
            {
                throw new Exception("初始化存在的索引名称失败");
            }
            foreach (var index in indices)
            {
                if (!IndexNames.ContainsKey(index))
                {
                    notExistsIndices.Add(index);
                    AddNotExistsIndexName(index);
                }
            }
            return notExistsIndices.ToArray();
        }

        /// <summary>
        /// 往IndexNames增加索引
        /// </summary>
        /// <param name="indexName"></param>
        public static void AddIndexName(string indexName)
        {
            if (!IndexNames.ContainsKey(indexName))
            {
                IndexNames.TryAdd(indexName, null);
            }
        }

        /// <summary>
        /// Initializes the exists index names.
        /// </summary>
        /// <returns>true 初始化成功，false 初始化失败</returns>
        public static bool InitExistsIndexNames()
        {
            var aliasResp = GetElasticClient().Indices.GetAlias();
            if (aliasResp.IsValid)
            {
                foreach (var index in aliasResp.Indices.Keys)
                {
                    if (!IndexNames.ContainsKey(index.Name))
                    {
                        IndexNames.TryAdd(index.Name, null);
                    }
                }
                return true;
            }
            return false;
        }

        /// <summary>
        /// Adds the name of the not exists index.
        /// </summary>
        /// <param name="indexName">Name of the index.</param>
        private static void AddNotExistsIndexName(string indexName)
        {
            if (!NotExistsIndexNames.ContainsKey(indexName))
            {
                NotExistsIndexNames.TryAdd(indexName, DateTime.Now);
            }
            else
            {
                NotExistsIndexNames[indexName] = DateTime.Now;
            }
        }

        /// <summary>
        /// 通过对ES的API接口发起请求校验一个索引名称是否存在
        /// </summary>
        /// <param name="indexName">Name of the index.</param>
        /// <returns><c>true</c> if XXXX, <c>false</c> otherwise.</returns>
        private static bool ExistsIndexByRequest(string indexName)
        {
            try
            {
                var request = Indices.Index(indexName);
                var resp = GetElasticClient().Indices.Exists(request);
                if (resp.Exists)
                {
                    IndexNames.TryAdd(indexName, null);
                    return true;
                }
                return false;
            }
            catch (Exception ex)
            {
                Console.WriteLine($"检查索引是否存在出错，IndexName：{indexName}");
                return false;
            }
        }

        /// <summary>
        /// To the query string.
        /// </summary>
        /// <typeparam name="TEntity">The type of the t entity.</typeparam>
        /// <param name="searchDesc">The search desc.</param>
        /// <returns>System.String.</returns>
        public static string ToQueryString<T>(Func<T, T> queryFunc) where T : new()
        {
            var settings = new ConnectionSettings().DefaultFieldNameInferrer(x => x);
            SerializableData<T> serializableData = queryFunc(new T());
            using (var stream = new MemoryStream())
            {
                serializableData.Write(stream, settings);
                stream.Position = 0;
                using (var sr = new StreamReader(stream))
                {
                    return sr.ReadToEnd();
                }
            }
        }

        /// <summary>
        /// 在ES中通过SQL的方式查询数据
        /// </summary>
        /// <typeparam name="TResult">返回的集合中数据实体类型.</typeparam>
        /// <param name="sql">要执行的SQL.</param>
        /// <returns>SQL执行返回的数据对象</returns>
        /// <exception cref="Exception"></exception>
        public static async Task<TResult> ExecuteSQLToSingleAsync<TResult>(string sql)
        {
            var fetchSize = 1;
            var sqlQuery = new QuerySqlRequest { Query = sql, FetchSize = fetchSize, Format = "json" };
            var resp = await GetElasticClient().Sql.QueryAsync(sqlQuery);
            if (!resp.IsValid || resp.Rows == null || resp.Rows.Count == 0)
                throw new Exception($"执行ES查询出错,Error:{resp.GetErrorString()}， SQL：{sql}", resp.OriginalException);

            var index = 0;
            var columnIndex = resp.Columns.ToDictionary(x => x.Name, x => index++);
            var row = resp.Rows.FirstOrDefault();
            var type = typeof(TResult);
            var result = default(TResult);
            if (type.IsClass)
            {
                var indexPropertyMap = ExtractColumnIndexPropertyMap<TResult>(columnIndex);
                result = DeserializationJsonRow<TResult>(row, indexPropertyMap);
            }
            else
            {
                result = (TResult)Convert.ChangeType(row[0].As<object>(), type);
            }
            if (!string.IsNullOrWhiteSpace(resp.Cursor))
            {
                await GetElasticClient().Sql.ClearCursorAsync(new ClearSqlCursorRequest { Cursor = resp.Cursor });
            }
            return result;
        }

        /// <summary>
        /// 在ES中通过SQL的方式查询数据
        /// </summary>
        /// <typeparam name="TResult">返回的集合中数据实体类型.</typeparam>
        /// <param name="sql">要执行的SQL.</param>
        /// <returns>SQL执行返回的数据对象</returns>
        /// <exception cref="Exception"></exception>
        public static async Task<IEnumerable<TResult>> ExecuteSQLToBaseAsync<TResult>(string sql)
        {
            var fetchSize = 1000;
            var sqlQuery = new QuerySqlRequest { Query = sql, FetchSize = fetchSize, Format = "json" };
            var resp = await GetElasticClient().Sql.QueryAsync(sqlQuery);
            if (!resp.IsValid)
                throw new Exception($"执行ES查询出错,Error:{resp.GetErrorString()}， SQL：{sql}", resp.OriginalException);
            if (resp.Rows == null || resp.Rows.Count == 0)
                return new TResult[0];

            var result = new List<TResult>(fetchSize);
            var type = typeof(TResult);
            foreach (var row in resp.Rows)
            {
                result.Add((TResult)Convert.ChangeType(row[0].As<object>(), type));
            }

            while (resp.Rows != null && resp.Rows.Count >= fetchSize && !string.IsNullOrWhiteSpace(resp.Cursor))
            {
                sqlQuery = new QuerySqlRequest { Cursor = resp.Cursor };
                resp = await GetElasticClient().Sql.QueryAsync(sqlQuery);
                foreach (var row in resp.Rows)
                {
                    result.Add((TResult)Convert.ChangeType(row[0].As<object>(), type));
                }
            }
            if (!string.IsNullOrWhiteSpace(resp.Cursor))
            {
                await GetElasticClient().Sql.ClearCursorAsync(new ClearSqlCursorRequest { Cursor = resp.Cursor });
            }
            return result;
        }

        /// <summary>
        /// 支持复杂对象（属性为对象，非值类型）
        /// </summary>
        /// <typeparam name="TResult"></typeparam>
        /// <param name="columnIndexs"></param>
        /// <returns></returns>
        private static Dictionary<int, List<PropertyInfo>> ExtractColumnIndexPropertyMapV2<TResult>(Dictionary<string, int> columnIndexs)
        {
            var resultType = typeof(TResult);
            var indexPropertyMap = new Dictionary<int, List<PropertyInfo>>();
            var propertyQueue = new Queue<(PropertyInfo, List<PropertyInfo>)>(resultType.GetProperties().Select(x => (x, new List<PropertyInfo>())));
            while (propertyQueue.Count > 0)
            {
                var item = propertyQueue.Dequeue();
                var propertyInfo = item.Item1;
                var parents = item.Item2;
                if (!propertyInfo.CanWrite)
                {
                    continue;
                }
                var newParents = new List<PropertyInfo>(parents);
                newParents.Add(propertyInfo);
                var propertyName = string.Join(".", newParents.Select(x => x.Name));
                var kv = columnIndexs.FirstOrDefault(x => x.Key.Equals(propertyName, StringComparison.OrdinalIgnoreCase));
                if (!string.IsNullOrWhiteSpace(kv.Key) && !indexPropertyMap.ContainsKey(kv.Value))
                {
                    indexPropertyMap.Add(kv.Value, newParents);
                    continue;
                }
                else if (propertyInfo.PropertyType.IsClass && propertyInfo.PropertyType != typeof(String))
                {
                    var subProperties = propertyInfo.PropertyType.GetProperties();
                    foreach (var subPropertyInfo in subProperties)
                    {
                        if (!propertyInfo.CanWrite)
                        {
                            continue;
                        }
                        propertyQueue.Enqueue((subPropertyInfo, newParents));
                    }
                }
            }
            return indexPropertyMap;
        }

        /// <summary>
        /// 在ES中通过SQL的方式查询数据
        /// 支持复杂对象（存在属性为对象，非值类型）
        /// </summary>
        /// <typeparam name="TResult">返回的集合中数据实体类型.</typeparam>
        /// <param name="sql">要执行的SQL.</param>
        /// <returns>SQL执行返回的数据对象</returns>
        /// <exception cref="Exception"></exception>
        public static async Task<IEnumerable<TResult>> ExecuteSQLAsyncV2<TResult>(string sql, int fetchSize = 1000)
            where TResult : class, new()
        {
            var sqlQuery = new QuerySqlRequest { Query = sql, FetchSize = fetchSize, Format = "json" };
            var resp = await GetElasticClient().Sql.QueryAsync(sqlQuery);
            if (!resp.IsValid)
                throw new Exception($"执行ES查询出错,Error:{resp.GetErrorString()}， SQL：{sql}", resp.OriginalException);
            if (resp.Rows == null || resp.Rows.Count == 0)
                return new TResult[0];

            var index = 0;
            var columnIndex = resp.Columns.ToDictionary(x => x.Name, x => index++);
            var indexPropertyMap = ExtractColumnIndexPropertyMapV2<TResult>(columnIndex);
            var result = new List<TResult>(fetchSize);
            var rows = DeserializationJsonRowsV2<TResult>(resp.Rows, indexPropertyMap);
            result.AddRange(rows);

            while (resp.Rows != null && resp.Rows.Count >= fetchSize && !string.IsNullOrWhiteSpace(resp.Cursor))
            {
                sqlQuery = new QuerySqlRequest { Cursor = resp.Cursor };
                resp = await GetElasticClient().Sql.QueryAsync(sqlQuery);
                rows = DeserializationJsonRowsV2<TResult>(resp.Rows, indexPropertyMap);
                result.AddRange(rows);
            }
            if (!string.IsNullOrWhiteSpace(resp.Cursor))
            {
                await GetElasticClient().Sql.ClearCursorAsync(new ClearSqlCursorRequest { Cursor = resp.Cursor });
            }
            return result;
        }

        /// <summary>
        /// 反序列化Json格式的行数据
        /// </summary>
        /// <typeparam name="TResult"></typeparam>
        /// <param name="rows"></param>
        /// <param name="indexPropertyMap"></param>
        /// <returns></returns>
        private static List<TResult> DeserializationJsonRowsV2<TResult>(IReadOnlyCollection<SqlRow> rows, Dictionary<int, List<PropertyInfo>> indexPropertyMap) where TResult : class, new()
        {
            if (rows == null || rows.Count == 0)
                return new List<TResult>();
            var result = new List<TResult>();
            foreach (var row in rows)
            {
                var item = DeserializationJsonRowV2<TResult>(row, indexPropertyMap);
                result.Add(item);
            }
            return result;
        }

        /// <summary>
        /// 反序列化Json格式的行数据
        /// </summary>
        /// <typeparam name="TResult"></typeparam>
        /// <param name="rows"></param>
        /// <param name="indexPropertyMap"></param>
        /// <returns></returns>
        private static TResult DeserializationJsonRowV2<TResult>(SqlRow row, Dictionary<int, List<PropertyInfo>> indexPropertyMap)
        {
            var item = Activator.CreateInstance<TResult>();
            for (var i = 0; i < row.Count; i++)
            {
                if (!indexPropertyMap.ContainsKey(i))
                    continue;
                if (row[i] == null)
                {
                    continue;
                }
                var propertys = indexPropertyMap[i];
                object target = item;
                for (int j = 0; j < propertys.Count; j++)
                {
                    if (j == propertys.Count - 1)
                    {
                        object value;
                        if (propertys[j].PropertyType == typeof(int))
                        {
                            value = Convert.ToInt32(row[i].As<object>());
                        }
                        else
                        {
                            value = row[i].As(propertys[j].PropertyType);
                        }
                        propertys[j].SetValue(target, value);
                    }
                    else
                    {
                        target = propertys[j].GetValue(target);
                        if (target == null)
                        {
                            target = Activator.CreateInstance(propertys[j].PropertyType);
                        }
                    }
                }
            }
            return item;
        }
    }

    /// <summary>
    /// Class IsoDateFormatSerializer.
    /// </summary>
    /// <seealso cref="Nest.JsonNetSerializer.ConnectionSettingsAwareSerializerBase" />
    public class IsoDateFormatSerializer : ConnectionSettingsAwareSerializerBase
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="IsoDateFormatSerializer"/> class.
        /// </summary>
        /// <param name="builtinSerializer">The builtin serializer.</param>
        /// <param name="connectionSettings">The connection settings.</param>
        public IsoDateFormatSerializer(IElasticsearchSerializer builtinSerializer, IConnectionSettingsValues connectionSettings)
            : base(builtinSerializer, connectionSettings) { }

        /// <summary>
        /// Creates the json serializer settings.
        /// </summary>
        /// <returns>JsonSerializerSettings.</returns>
        protected override JsonSerializerSettings CreateJsonSerializerSettings() => new JsonSerializerSettings
        {
            DateFormatHandling = DateFormatHandling.IsoDateFormat,
            DateParseHandling = DateParseHandling.DateTimeOffset,
            DateTimeZoneHandling = DateTimeZoneHandling.RoundtripKind,
            Formatting = Formatting.Indented,
            DateFormatString = "yyyy-MM-dd HH:mm:ss"
        };
    }

    public static class ElasticSearchExtension
    {
        /// <summary>
        /// Gets the error string.
        /// </summary>
        /// <param name="response">The response.</param>
        /// <returns>System.String.</returns>
        public static string GetErrorString(this IResponse response)
        {
            if (response == null)
                return string.Empty;
            var failedShards = response.ServerError?.Error?.FailedShards;
            if (failedShards == null || failedShards.Count == 0)
                return response.ServerError?.Error?.ToString() ?? string.Empty;

            var errorBuilder = new StringBuilder();
            foreach (var failedShard in failedShards)
            {
                errorBuilder.AppendLine(failedShard.Reason.ToString());
            }
            return errorBuilder.ToString();
        }
    }
}
